Skip to content

Shorten the default Android keyframe interval to one second - #103

Open
krystofwoldrich wants to merge 1 commit into
mainfrom
codex/shorter-android-keyframe-interval
Open

krystofwoldrich wants to merge 1 commit into
mainfrom
codex/shorter-android-keyframe-interval

Conversation

@krystofwoldrich

Copy link
Copy Markdown
Collaborator

Android streams currently default to ten seconds between keyframes, which can leave a decoder waiting for the next recovery point after losing reference frames. Shorten the shared scrcpy and gRPC default to one second, update CLI help and the generated documentation, and add a patch changeset. Explicit interval overrides and on-demand keyframe requests remain unchanged.

For host FFmpeg encoding this is a frame-count interval: at a configured 60 FPS, the GOP changes from 600 to 60 frames. Actual wall-clock spacing grows when the source produces fewer frames. More frequent keyframes trade some compression efficiency for more frequent recovery opportunities; this does not change WebRTC feedback handling.

Validation:

  • bun run --filter serve-emu check passed: 973 tests, coverage checks, server/UI/test type checks, build, and package smoke test. Existing integration tests verify the one-second default for scrcpy and both gRPC encoder backends, including encoder replacement after a size change.
  • Tested the built module on the staging Android machine with NVENC, RGB888 over gRPC, WebRTC, 60 FPS, 8 Mbps, and a 960-pixel maximum stream dimension. Confirmed -g 60 and four keyframes in every 240-frame health window after warm-up.
  • Sampled the animated test page's health endpoint every two seconds for one minute. After warm-up, gRPC delivery averaged 52.79 FPS and encoded/published output averaged 54.29 FPS (48.49–58.49), with no capture restarts or additional publisher drops.
  • Twenty browser samples in two batches over 64 seconds showed 31–51 client FPS (43.5 average). One sample reported two brief freezes totaling 508 ms. No multi-second freeze was captured, but the baseline was disrupted by app/resolution changes, so this experiment does not establish that keyframe recovery caused the original freezes.

— Codex (GPT-6)

Co-authored-by: Codex (GPT-6) <noreply@openai.com>

@krystofwoldrich-agent krystofwoldrich-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 This is an automated review. Addressing it doesn't guarantee a merge.

Review: Shorten the default Android keyframe interval to one second

Verdict: APPROVE
Findings: 0 critical · 0 design · 1 suggestion · 0 nit

The change lowers SCRCPY_DEFAULTS.keyFrameInterval from 10 to 1 and updates the CLI help, both README copies, the changeset, and the default-asserting tests. The default is consumed consistently: scrcpy passes it as i-frame-interval (seconds, MediaCodec semantics), and every FFmpeg backend in h264-encoder.ts derives keyint/-g from fps × keyFrameInterval, so all paths move together. The Hub's serve-emu-options.ts does not set keyFrameInterval, so the Hub inherits the new default, which makes the expo-device-hub changeset correct given that serve-emu is in the changeset ignore list. The generated README under packages/serve-emu/packages/serve-emu/ matches the source README row. Tests cover the scrcpy codec option and both gRPC encoder backends via the existing test.each.

Findings

[suggestion] Consider RTCP PLI handling as the targeted fix for WebRTC reference-frame loss

packages/serve-emu/packages/serve-emu/src/scrcpy.ts:137
webrtc-publisher.ts requests a keyframe on peer open, track open, and backpressure, and it wires an RtcpNackResponder, but I found no handling of Picture Loss Indication from the browser. That means a decoder that loses reference frames beyond what NACK retransmission covers has no way to ask for a recovery point, which matches the motivation in the PR description. A 1s GOP is a reasonable blanket mitigation, but at CBR (-maxrate/-bufsize equal to the bit rate) a keyframe every 60 frames takes a larger share of the 8 Mbps budget and lowers P-frame quality for static UI. Forwarding PLI to onKeyframeRequest would let the interval go back up later without losing recovery. Not blocking for this PR; the description already notes the experiment did not confirm keyframe recovery as the cause of the original freezes, so this is a follow-up idea.

keyFrameInterval: 10,
// Frequent keyframes give decoders another recovery point after lost frames.
// Late joiners can still request an immediate keyframe via reset-video.
keyFrameInterval: 1,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] webrtc-publisher.ts requests a keyframe on peer open, track open, and backpressure, and wires an RtcpNackResponder, but I found no handling of RTCP Picture Loss Indication from the browser. A decoder that loses reference frames beyond NACK recovery therefore has no way to ask for a recovery point, which matches this PR's motivation. The 1s GOP is a reasonable blanket mitigation, but at CBR (-maxrate/-bufsize equal to the bit rate) a keyframe every 60 frames takes a larger share of the 8 Mbps budget and lowers P-frame quality on static UI. Forwarding PLI to onKeyframeRequest would address the root cause and would let this interval go back up later. Not blocking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants